package animation;

import com.crunch.math.Vector2f;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * A collection of skin components used to create skins.
 */
public class SkinComponentCollection {
	public SkinComponentCollection() {
		components = new ArrayList<Component>();
		componentNames = new HashMap<String, Integer>();
	}

	public void save(Path path)
			throws IOException {
		Writer writer = null;
		try {
			JSONObject rootObj = new JSONObject();

			JSONObject propertiesObj = new JSONObject();
			rootObj.put("properties", propertiesObj);
			propertiesObj.put("scale", scale);

			JSONArray componentsArr = new JSONArray();
			rootObj.put("components", componentsArr);

			for (Component comp : components) {
				JSONObject componentObj = new JSONObject();
				componentsArr.put(componentObj);

				componentObj.put("name", comp.getName());
				Vector2f origin = comp.getOrigin();
				componentObj.put("origin", new JSONArray().put(origin.x()).put(origin.y()));
				Vector2f boundMin = comp.getBoundMin();
				componentObj.put("boundMin", new JSONArray().put(boundMin.x()).put(boundMin.y()));
				Vector2f boundMax = comp.getBoundMax();
				componentObj.put("boundMax", new JSONArray().put(boundMax.x()).put(boundMax.y()));
			}

			writer = Files.newBufferedWriter(path, Charset.defaultCharset());
			rootObj.write(writer);
		} catch (JSONException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to write " + path);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {}
			}
		}
	}

	public void load(Path path)
			throws IOException {
		components.clear();
		componentNames.clear();
		Reader reader = null;
		try {
			reader = Files.newBufferedReader(path, Charset.defaultCharset());
			JSONObject rootObj = new JSONObject(new JSONTokener(reader));

			JSONObject propertiesObj = rootObj.getJSONObject("properties");
			float scale = (float) propertiesObj.getDouble("scale");

			JSONArray componentsArr = rootObj.getJSONArray("components");
			for (int c = 0; c < componentsArr.length(); ++c) {
				JSONObject componentObj = componentsArr.getJSONObject(c);

				String name = componentObj.getString("name");
				JSONArray originArr = componentObj.getJSONArray("origin");
				Vector2f origin = new Vector2f(
						(float) originArr.getDouble(0),
						(float) originArr.getDouble(1));
				JSONArray boundMinArr = componentObj.getJSONArray("boundMin");
				Vector2f boundMin = new Vector2f(
						(float) boundMinArr.getDouble(0),
						(float) boundMinArr.getDouble(1));
				JSONArray boundMaxArr = componentObj.getJSONArray("boundMax");
				Vector2f boundMax = new Vector2f(
						(float) boundMaxArr.getDouble(0),
						(float) boundMaxArr.getDouble(1));

				if (name.isEmpty()) {
					throw new IOException("Invalid name");
				}

				// make sure it is not duplicate
				if (componentNames.containsKey(name)) {
					throw new IOException("Duplicate component specified");
				}

				Component component = addComponent(name);
				component.setOrigin(origin);
				component.setBoundMin(boundMin);
				component.setBoundMax(boundMax);
			}
		} catch (JSONException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to read " + path);
		} catch (IOException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to read " + path);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {}
			}
		}
	}

	public static class Component {
		private Component(String name) {
			this.name = name;
			origin = new Vector2f();
			boundMin = new Vector2f();
			boundMax = new Vector2f();
		}

		public String getName() {
			return name;
		}

		public void setOrigin(Vector2f origin) {
			this.origin.set(origin);
		}

		public Vector2f getOrigin() {
			return new Vector2f(origin);
		}

		public void setBoundMin(Vector2f boundMin) {
			this.boundMin.set(boundMin);
		}

		public Vector2f getBoundMin() {
			return new Vector2f(boundMin);
		}

		public void setBoundMax(Vector2f boundMax) {
			this.boundMax.set(boundMax);
		}

		public Vector2f getBoundMax() {
			return new Vector2f(boundMax);
		}

		private String name;
		private Vector2f origin;
		private Vector2f boundMin;
		private Vector2f boundMax;
	}

	////////
	// accesses the skin component collection

	public int getComponentCount() {
		return components.size();
	}

	public Component getComponent(int index) {
		return components.get(index);
	}

	public Component getComponent(String name) {
		Integer index = componentNames.get(name);
		return (index == null) ? null : components.get(index);
	}

	public int getComponentIndex(String name) {
		Integer index = componentNames.get(name);
		return (index == null) ? -1 : index;
	}

	public float getScale() {
		return scale;
	}

	////////
	// modifies the skin component collection

	public Component addComponent(String name) {
		if (componentNames.containsKey(name)) {
			throw new IllegalArgumentException("Component " + name + " already exists");
		}
		Component component = new Component(name);
		int index = components.size();
		components.add(component);
		componentNames.put(component.getName(), index);
		return component;
	}

	public void removeComponent(int index) {
		componentNames.remove(components.get(index).getName());
		components.remove(index);
		for (Map.Entry<String, Integer> e : componentNames.entrySet()) {
			if (e.getValue() > index) {
				e.setValue(e.getValue() - 1);
			}
		}
	}

	public void setComponentName(int index, String name) {
		if (index < 0 || index >= components.size()) {
			throw new IllegalArgumentException("Invalid component index");
		}
		Integer c = componentNames.get(name);
		if (c != null && c != index) {
			throw new IllegalArgumentException("Component " + name + " already exists");
		}

		Component component = components.get(index);
		componentNames.remove(component.name);
		component.name = name;
		componentNames.put(name, index);
	}

	public void setScale(float scale) {
		this.scale = scale;
	}

	private float scale;
	private ArrayList<Component> components;
	private HashMap<String, Integer> componentNames;
}
